#include "OP_Extrude.h"
#include "../Geometries/PolygonMesh.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_Extrude::OP_Extrude (vector<CIView::View*>& v)
: Operation (v), geometry (0) {}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_Extrude::~OP_Extrude() {
    if (status != COMPLETED ) {

        //Deleting created vertices
        map<Vertex*,Vertex*>::iterator v = clone.begin();
        while (v != clone.end()) {
            delete v->second;              
            v++;
        }

        //Deleting the created geometries and faces
        for (int i=0; i<oldGeometries.size(); i++) {
            VertexLib& newVertexLib = newGeometries[i]->GetVertices();
            FaceLib& oldFaceLib = oldGeometries[i]->GetFaces();
            FaceLib& newFaceLib = newGeometries[i]->GetFaces();

            //Deleting all created faces
            for (int j=oldFaceSize[i]; j<newFaceLib.size(); j++)
                delete newFaceLib[j];

            newVertexLib.clear();
            newFaceLib.clear();

            delete newGeometries[i];
        }
    }
    else {
        //Deleting the old geometries 
        for (int i=0; i<oldGeometries.size(); i++) {
            oldGeometries[i]->GetVertices().clear();
            oldGeometries[i]->GetFaces().clear();

            delete oldGeometries[i];
        }
    }
}










///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_Extrude::Exec() {
    Vector3D& TranslateParams = OperationManager::X_Y_Z_Params;

    if (TranslateParams.x == 0 && TranslateParams.y == 0 && TranslateParams.z == 0) {
        status = ZOMBIE;
        return status;
    }

    ExtrudeSelected();                                          //Extrudes the selected faces

    translationMatrix.SetTranslation (TranslateParams.x, 
                                      TranslateParams.y, 
                                      TranslateParams.z);       //The matrix to apply 
    
    //Apply the translation to the vertices selected for the extrusion
    map<Vertex*,Vertex*>::iterator v = clone.begin();

    while (v != clone.end()) {
        v->first->Transform (translationMatrix);            //Applying the translation to the vertices extruded
        v++;
    }

    inverseMatrix = translationMatrix.GetInverse();         //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                          //Updates the 3D views

    status = COMPLETED;
    
    return status;        
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_Extrude::Undo() {
    status = UNDO;

    //Removing all the new geometries and readding the old geoemtries
    for (int i=0; i<oldGeometries.size(); i++) {
        scene.RemoveGeometry (newGeometries[i]->GetName().c_str());
        scene.AddGeometry (oldGeometries[i]);
    }
    scene.ApplyMatrix (inverseMatrix);  //Applies the last inverse matrix

    UpdateViews();                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_Extrude::Redo() {
    status = COMPLETED;

    scene.ApplyMatrix (translationMatrix);  //Applies the last translation matrix

    //Readding all the new geometries and removing the old geoemtries
    for (int i=0; i<oldGeometries.size(); i++) {
        scene.RemoveGeometry (oldGeometries[i]->GetName().c_str());
        scene.AddGeometry (newGeometries[i]);
    }

    UpdateViews();                          //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_Extrude::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);                    //Unproject the mouse cursor coordinates

    //Computing the mouse click coordinates (in world coordinates)
    switch (view->GetCameraType()) {
        case Camera::TOP:
            click.x = x;
            click.z = y;
            break;
        
        case Camera::FRONT:
            click.x = x;
            click.y = y;
            break;
        
        case Camera::RIGHT:            
            click.y = y;
            click.z = x;
            break;
        
        case Camera::USER:          
            click.x = x;
            click.y = y;

            break;
    }    

    ExtrudeSelected();
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_Extrude::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    if (drag.Length() > 0)
        status = COMPLETED;
    else
        status = ZOMBIE;
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_Extrude::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates    

    //Computing the mouse drag value
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = x - click.x;
            drag.z = y - click.z;
            break;
        
        case Camera::FRONT:
            drag.x = x - click.x;
            drag.y = click.y - y;
            break;
        
        case Camera::RIGHT:            
            drag.y = click.y - y;
            drag.z = click.z - x;
            break;

        case Camera::USER:          //TODO -> Consider the camera orientation on the z axis too!!!
            Orientation& cameraOrient = view->GetCameraOrientation();       //Gets the current camera orientation

            //Computing the drag on the x axis (depends on the x, y and z camera angles)
            if ((cameraOrient.y >= 300 || cameraOrient.y <= 60))
                drag.x = x - click.x;
            else if ((cameraOrient.y >=120 && cameraOrient.y <= 240))
                drag.x = click.x - x;
            else if ((cameraOrient.y > 60 && cameraOrient.y < 120)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.x = y - click.y;
                else
                    drag.x = click.y - y;
            }
            else if ((cameraOrient.y < 300 && cameraOrient.y > 240)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.x = click.y - y;
                else
                    drag.x = y - click.y;                
            }

            //Computing the drag on the y axis (depends on the x and z camera angles)
            if (cameraOrient.x >= 90 && cameraOrient.x <= 270)
                drag.y = y - click.y;
            else
                drag.y = click.y - y;

            //Computing the drag on the z-axis (depends on the x, y and z camera angles)
            if ((cameraOrient.y >= 300 || cameraOrient.y <= 60)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.z = click.y - y;
                else
                    drag.z = y - click.y;
            }
            else if ((cameraOrient.y >=120 && cameraOrient.y <= 240)) {
                if (cameraOrient.x > 180 && cameraOrient.x < 360)
                    drag.z = y - click.y;
                else
                    drag.z = click.y - y;
            }
            else if ((cameraOrient.y > 60 && cameraOrient.y < 120))
                drag.z = x - click.x;
            else if ((cameraOrient.y < 300 && cameraOrient.y > 240))
                drag.z = click.x - x;
            break;
    }    

    if (drag.Length() == 0)
        Undo();   

    translationMatrix.SetTranslation (drag.x, drag.y, drag.z);
    Matrix toApply;
    toApply = inverseMatrix*translationMatrix;       //The matrix to apply for the transformation has to consider the previous transformation

    //Apply the translation to the vertices selected for the extrusion
    map<Vertex*,Vertex*>::iterator v = clone.begin();

    while (v != clone.end()) {
        v->first->Transform(toApply);              //Applying the translation to the vertices extruded
        v++;
    }
    inverseMatrix = translationMatrix.GetInverse();         //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                          //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_Extrude::WheelEvent (CIView::View* view, QWheelEvent* e) {
}

/// <summary> 
/// Method called to extrude the selected faces
/// </summary>
void CIOps::OP_Extrude::ExtrudeSelected() {
    map<const char*, Geometry*>& geometries = scene.GetGeometries();    //Gets a reference to the geometries in the scene
    map<const char*, Geometry*>::iterator it = geometries.begin();  
    FaceIter face;

    //For each geometry that has at least one face selected,
    //we create a new geometry, representing the result of the extrusion
    while (it != geometries.end()) {        
        if (it->second->hasPrimitivesSelected) {
            VertexLib& oldVertexLib = it->second->GetVertices();
            FaceLib& oldFaceLib = it->second->GetFaces();
            FaceLib toExtrudeFaces;

            infoEdgeSharing.clear();

            face = oldFaceLib.begin();

            while (face != oldFaceLib.end()) {      //For each face of the current geometry
                if ((*face)->selected) {            //If the face is selected
                    if (!geometry) {                                            //If the extrusion of the current geometry, starts now, we have to create a new geometry
                        unsigned int id = scene.GenerateNewGeometryID();        //gets a reference to the number of geometries that have been added to the scene
                        stringstream name;
                        name << "Mesh" << id; 
                        geometry = new PolygonMesh (name.str());                //creates the new geometry 
                        
                        //Inserting all vertices of the old geometry in the new geometry
                        VertexLib& newVertexLib = geometry->GetVertices();
                        newVertexLib.insert (newVertexLib.begin()+newVertexLib.size(), oldVertexLib.begin(), oldVertexLib.end());  

                        //Inserting all faces of the old geometry in the new geometry
                        FaceLib& newFaceLib = geometry->GetFaces();
                        newFaceLib.insert (newFaceLib.begin()+newFaceLib.size(), oldFaceLib.begin(), oldFaceLib.end());
                    }

                    const VertexLib& faceVertices = (*face)->vertices;
                    map<Vertex*, map<Vertex*, bool> >::iterator infoEdgeSharingIt;    
                    map<Vertex*, bool>::iterator edgeIt;

                    //In this loop we save the info about the vertices already inserted in the new geometry
                    //and we determine information about if the generic edge is shared
                    //between two different faces or not; we store this information inside
                    //the infoEdgeSharing map
                    for (int i=0; i<faceVertices.size(); i++) {
                        inserted[faceVertices[i]] = true;           //Saving the info that this vertex has been already inserted in the new geometry

                        infoEdgeSharingIt = infoEdgeSharing.find (faceVertices[i]);

                        if (infoEdgeSharingIt != infoEdgeSharing.end()) {
                            edgeIt = infoEdgeSharingIt->second.find (faceVertices[(i+1)%faceVertices.size()]);

                            if (edgeIt != infoEdgeSharingIt->second.end()) {    //In this case we have determined that the current edge is shared
                                edgeIt->second = true;
                                infoEdgeSharing[faceVertices[(i+1)%faceVertices.size()]][faceVertices[i]] = true;
                            }
                            else {                                              //In this case it's the first time we see this edge
                                infoEdgeSharingIt->second[faceVertices[(i+1)%faceVertices.size()]] = false;
                                infoEdgeSharing[faceVertices[(i+1)%faceVertices.size()]][faceVertices[i]] = false;
                            }
                        }
                        else {                                                  //In this case it's the first time we see these vertices
                            infoEdgeSharing[faceVertices[i]][faceVertices[(i+1)%faceVertices.size()]] = false;
                            infoEdgeSharing[faceVertices[(i+1)%faceVertices.size()]][faceVertices[i]] = false;
                        }
                    }

                    toExtrudeFaces.push_back (*face);           //stores a pointer to a face that has to be extruded          
                }
                face++;
            }

            if (geometry) {                                     //If a new geometry has been created

                face = toExtrudeFaces.begin();

                while (face != toExtrudeFaces.end()) {          //for each face to be extruded       
                    Extrude (*face);                            //extrudes the face
                    face++;
                }

                newGeometries.push_back (geometry);             //saves a pointer to the created geometry
                oldGeometries.push_back (it->second);           //saves a pointer to the old geometry

                oldVertexSize.push_back (oldVertexLib.size());  //saves the number of vertices of the geometry before extrusion
                oldFaceSize.push_back (oldFaceLib.size());      //saves the number of faces of the geometry before extrusion

                geometry = 0;                                   //resets the the pointer to the current geometry created
            }
        }
        
        it++;
    }
    
    //Removing the old geometries and adding the new geometries, result of the the extrusion
    for (int i=0; i<oldGeometries.size(); i++) {
        scene.RemoveGeometry (oldGeometries[i]->GetName().c_str());
        scene.AddGeometry (newGeometries[i]);
    }

    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Method called to extrude a face: 
/// a face is extruded creating a polygon of triangles
/// </summary>
/// <param name="face"> The face to be extruded </param>
void CIOps::OP_Extrude::Extrude (Face* face) {
    const VertexLib& vertices = face->vertices;

    //Each vertex of the original face, has a "clone" 
    Vertex* prev_clone = 0;             //The clone used in the previous iteration
    Vertex* prev_original = 0;          //The original vertex associated to the clone used in the previous iteration

    //The coordinates of the current triangle
    Vertex* v1;
    Vertex* v2; 
    Vertex* v3;
    
    map<Vertex*, map<Vertex*, bool> >::iterator isSharedEdge;  //Iterator used to access the infoEdge sharing

    if (vertices.size() < 2)            //If the face is formed only by 2 or less vertices, we must return
        return;        

    int size = vertices.size();         //The number of vertices of the face

    //For the first iteration we need as clone the clone of the last vertex of the face
    prev_original = vertices[size-1];
    prev_clone = clone[prev_original];
    
    if (!prev_clone)                    //If it has not been created yet, we must create it
        prev_clone = clone[prev_original] = new Vertex (*prev_original); 
        
    //The loop to perform the extrusion
    for (int i=0; i<size; i++) {
        v1 = vertices[i];
        v2 = clone[v1];
        v3 = prev_clone;

        if (!v2) 
            v2 = clone[v1] = new Vertex (*v1);                           

        isSharedEdge = infoEdgeSharing.find (v1);       //Finds info about the edges concerning the current vertex v1

        if (!(isSharedEdge->second[prev_original]))     //Only if the edge (v1,prev_original) is not shared between two faces, the new face has to be added            
            AddFace (v1, v3, v2); 

        v3 = vertices[(i+1)%size];
        
        if (!(isSharedEdge->second[v3]))                //Only if the edge (v1,v3) is not shared between two faces, the new face has to be added                                
            AddFace (v1, v2, v3);              
        
        prev_clone = v2;                                //Updates the pointer to the prev_clone for the next iteration
        prev_original = v1;                             //Updates the pointer to the prev_original for the next iteration
    }
}

/// <summary> 
/// Method called to add a face formed by the three input vertices
/// to the current geometry extruded
/// </summary>
/// <param name="v1"> The first vertex of the face </param>
/// <param name="v2"> The second vertex of the face </param>
/// <param name="v3"> The third vertex of the face </param>
void CIOps::OP_Extrude::AddFace (Vertex* v1, Vertex* v2, Vertex* v3) {
    VertexLib tmp (3);
    Face* f;

    if (!(inserted[v2])) {                      //If the v2 vertex has not been added yet, we must add it in the new geometry
        geometry->GetVertices().push_back (v2);
        inserted[v2] = true;
    }                        

    if (!(inserted[v3])) {                      //If the v3 vertex has not been added yet, we must add it in the new geometry
        geometry->GetVertices().push_back (v3);
        inserted[v3] = true;
    }      

    tmp[0] = v1;
    tmp[1] = v2;
    tmp[2] = v3;

    f = new Face (tmp);
    f->ComputeNormal();

    geometry->AddFace (f);
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/
